/**
 * 
 */
package idv.takeshi.software.productline.bookstore.application.service.dto;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Past;
import javax.validation.constraints.Pattern;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.annotation.DateTimeFormat.ISO;

import idv.takeshi.software.productline.bookstore.domain.model.useraccount.IndividualAccount;
import idv.takeshi.software.productline.bookstore.domain.model.useraccount.OrganizationAccount;
import idv.takeshi.software.productline.bookstore.domain.model.useraccount.Role;
import idv.takeshi.software.productline.bookstore.domain.model.useraccount.RoleDuplicateException;
import idv.takeshi.software.productline.bookstore.domain.model.useraccount.Sex;
import idv.takeshi.software.productline.bookstore.domain.model.useraccount.UserAccount;
import idv.takeshi.software.productline.bookstore.domain.model.useraccount.UserAccountException;
import idv.takeshi.software.productline.bookstore.domain.model.useraccount.UserAccountFactory;

/**
 * A data-transfer Object for Multi-UserAccount model.
 * @author takeshi
 *
 */
public class MultiUserAccountDto extends UserAccountDto<UserAccount> {
	
	@Pattern(regexp = ".+")
	@NotNull
	private String action;
	
	//following are individual user account properties
	@Pattern(regexp = ".*")
	private String lastName;
	@Pattern(regexp = ".*")
	private String firstName;
	
	@Pattern(regexp = "(MALE|FEMALE)")
	private String sex;
	
	@Past
	@DateTimeFormat(iso=ISO.DATE)
	private Date birthday;
	
	//following are organization user account properties
	@Pattern(regexp = ".*")
	private String name;
	
	//following are mutual user account properties
	private Long id;
	
	
	private String[] roles;
	
	private boolean enabled;
	
	/**
	 * e.g., idv.takeshi.software.productline.bookstore.domain.model.useraccount.OrganizationAccount.
	 */
	@NotNull
	@Pattern(regexp = ".+")
	private String userAccountType;
	
	
	@NotNull
	@Pattern(regexp = ".+")
	private String previousPath;
	
	/**
	 * Default constructor.
	 */
	public MultiUserAccountDto(){}
	
	/**
	 * Initial this DTO by given userAccount model.
	 * @param userAccount
	 */
	public MultiUserAccountDto(UserAccount userAccount){
		super(userAccount);
		
		this.id = userAccount.getId();
		this.userAccountType = userAccount.getClass().getName();
		List<String> tmpRoles = new ArrayList<String>();
		for(Role role : userAccount.getRoles()){
			tmpRoles.add(role.getClass().getName());
		}
		this.roles = tmpRoles.toArray(new String[]{});
		
		this.enabled = userAccount.isEnabled();
		
		if(userAccount instanceof IndividualAccount){
			IndividualAccount account = (IndividualAccount) userAccount;
			this.lastName = account.getLastName();
			this.firstName = account.getFirstName();
			this.sex = account.getSex().toString();
			this.birthday = account.getBirthday();
		} else if(userAccount instanceof OrganizationAccount){
			OrganizationAccount account = (OrganizationAccount) userAccount;
			this.name = account.getName();
		} else {
			throw new RuntimeException("userAccount instance:" + userAccount + " is not defined");
		}
	}

	/* (non-Javadoc)
	 * @see idv.takeshi.software.productline.bookstore.presentation.dto.UserAccountDto#createModel(idv.takeshi.software.productline.bookstore.domain.model.useraccount.UserAccountFactory)
	 */
	@Override
	public UserAccount createModel(UserAccountFactory userAccountFactory) throws UserAccountException {
		Validate.notNull(userAccountFactory, "userAccountFactory shall always not be null");
		UserAccount userAccount = null;
		
		userAccount = userAccountFactory.newInstance(this.userAccountType, this.getAccountName());
		
		userAccount.setAccountName(this.getAccountName());
		userAccount.setOriginalPassword(this.getPassword());
		userAccount.setZipCode(this.getZipCode());
		userAccount.setContactAddress(this.getContactAddress());
		userAccount.setContactPhoneNumber(this.getContactPhoneNumber());
		userAccount.setCellPhoneNumber(this.getCellPhoneNumber());
		userAccount.setEmailAddress(this.getEmailAddress());
		userAccount.setEnabled(enabled);
		
		for(String role : roles){
			userAccount.addRole(role);
		}
		
		if(userAccount instanceof IndividualAccount){
			IndividualAccount account = (IndividualAccount) userAccount;
			account.setLastName(lastName);
			account.setFirstName(firstName);
			account.setSex(Sex.valueOf(sex));
			account.setBirthday(birthday);
		} else if(userAccount instanceof OrganizationAccount){
			OrganizationAccount account = (OrganizationAccount) userAccount;
			account.setName(name);
		} else {
			throw new RuntimeException("userAccount instance:" + userAccount + " is not defined");
		}
		return userAccount;
	}

	/* (non-Javadoc)
	 * @see idv.takeshi.software.productline.bookstore.presentation.dto.UserAccountDto#loadModel(idv.takeshi.software.productline.bookstore.domain.model.useraccount.UserAccount)
	 */
	@Override
	public Collection<Role> loadModel(UserAccount userAccount) throws RoleDuplicateException {
		Validate.notNull(userAccount, "userAccount shall always not be null");
		Collection<Role> removeRoles = null;
		userAccount.setAccountName(this.getAccountName());
		if(StringUtils.isNotBlank(this.getConfirmedNewPassword()))
			userAccount.setOriginalPassword(this.getConfirmedNewPassword());
		userAccount.setZipCode(this.getZipCode());
		userAccount.setContactAddress(this.getContactAddress());
		userAccount.setContactPhoneNumber(this.getContactPhoneNumber());
		userAccount.setCellPhoneNumber(this.getCellPhoneNumber());
		userAccount.setEmailAddress(this.getEmailAddress());
		userAccount.setEnabled(enabled);
		
		removeRoles = syncRoles(userAccount);
		
		if(userAccount instanceof IndividualAccount){
			IndividualAccount account = (IndividualAccount) userAccount;
			account.setLastName(this.getLastName());
			account.setFirstName(this.getFirstName());
			account.setSex(Sex.valueOf(this.getSex()));
			account.setBirthday(this.getBirthday());
		} else if(userAccount instanceof OrganizationAccount){
			OrganizationAccount account = (OrganizationAccount) userAccount;
			account.setName(name);
		} else{
			throw new RuntimeException("userAccount instance:" + userAccount + " is not defined");
		}
		return removeRoles;
	}

	/**
	 * @return the id
	 */
	public Long getId() {
		return id;
	}

	/**
	 * @param id the id to set
	 */
	public void setId(Long id) {
		this.id = id;
	}

	/**
	 * @return the action
	 */
	public String getAction() {
		return action;
	}

	/**
	 * @param action the action to set
	 */
	public void setAction(String action) {
		this.action = action;
	}

	/**
	 * @return the lastName
	 */
	public String getLastName() {
		return lastName;
	}

	/**
	 * @param lastName the lastName to set
	 */
	public void setLastName(String lastName) {
		this.lastName = lastName;
	}

	/**
	 * @return the firstName
	 */
	public String getFirstName() {
		return firstName;
	}

	/**
	 * @param firstName the firstName to set
	 */
	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}

	/**
	 * @return the sex
	 */
	public String getSex() {
		return sex;
	}

	/**
	 * @param sex the sex to set
	 */
	public void setSex(String sex) {
		this.sex = sex;
	}

	/**
	 * @return the birthday
	 */
	public Date getBirthday() {
		return birthday;
	}

	/**
	 * @param birthday the birthday to set
	 */
	public void setBirthday(Date birthday) {
		this.birthday = birthday;
	}

	/**
	 * @return the name
	 */
	public String getName() {
		return name;
	}

	/**
	 * @param name the name to set
	 */
	public void setName(String name) {
		this.name = name;
	}

	/**
	 * @return the roles
	 */
	public String[] getRoles() {
		return roles;
	}

	/**
	 * @param roles the roles to set
	 */
	public void setRoles(String[] roles) {
		this.roles = roles;
	}

	/**
	 * @return the userAccountType
	 */
	public String getUserAccountType() {
		return userAccountType;
	}

	/**
	 * @param userAccountType the userAccountType to set
	 */
	public void setUserAccountType(String userAccountType) {
		this.userAccountType = userAccountType;
	}

	/**
	 * @return the previousPath
	 */
	public String getPreviousPath() {
		return previousPath;
	}

	/**
	 * @param previousPath the previousPath to set
	 */
	public void setPreviousPath(String previousPath) {
		this.previousPath = previousPath;
	}

	/**
	 * @return the enable
	 */
	public boolean isEnabled() {
		return enabled;
	}

	/**
	 * @param enable the enable to set
	 */
	public void setEnabled(boolean enable) {
		this.enabled = enable;
	}

	/* (non-Javadoc)
	 * @see idv.takeshi.software.productline.bookstore.presentation.dto.UserAccountDto#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) {
		if (obj == null) { return false; }
		   if (obj == this) { return true; }
		   if (obj.getClass() != getClass()) {
		     return false;
		   }
		   MultiUserAccountDto rhs = (MultiUserAccountDto) obj;
		   return new EqualsBuilder()
		       .append(this.action, rhs.action)
		       .append(this.lastName, rhs.lastName)
		       .append(this.firstName, rhs.firstName)
		       .append(this.sex, rhs.sex)
		       .append(this.birthday, rhs.birthday)
		       .append(this.name, rhs.name)
			   .append(this.id, rhs.id)
			   .append(this.roles, rhs.roles)
			   .append(this.userAccountType, rhs.userAccountType)
			   .append(this.previousPath, rhs.previousPath)
			   .append(this.enabled, rhs.enabled)
		       .isEquals();
	}

	/* (non-Javadoc)
	 * @see idv.takeshi.software.productline.bookstore.presentation.dto.UserAccountDto#hashCode()
	 */
	@Override
	public int hashCode() {
		return new HashCodeBuilder(23, 97).
	       append(this.action).
	       append(this.lastName).
	       append(this.firstName).
	       append(this.sex).
	       append(this.birthday).
	       append(this.name).
	       append(this.id).
	       append(this.roles).
	       append(this.userAccountType).
	       append(this.previousPath).
	       append(this.enabled).
	       toHashCode();
	}

	/**
	 * @param userAccount
	 * @throws RoleDuplicateException 
	 */
	private Collection<Role> syncRoles(UserAccount userAccount) throws RoleDuplicateException {
		List<Role> removeRoles = new ArrayList<Role>(); 
		boolean found = false;
		//remove roles
		for(Role role : userAccount.getRoles()){
			found = false;
			for(String roleName : this.roles){
				if(role.getClass().getName().equals(roleName)){
					found = true;
					break;
				}
			}
			if(!found){
				removeRoles.add(role);
			}
		}
		if(removeRoles.size() > 0){
			for(Role role : removeRoles){
				userAccount.removeRole(role.getClass());
			}
		}
		
		//add roles
		Set<String> tmpRoleNames = new HashSet<String>(Arrays.asList(this.roles));
		List<String> newRoleNames = new ArrayList<String>();
		for(String tmpRoleName : tmpRoleNames){
			found = false;
			for(Role role : userAccount.getRoles()){
				if(role.getClass().getName().equals(tmpRoleName)){
					found = true;
					break;
				}
			}
			if(!found)
				newRoleNames.add(tmpRoleName);
		}
		
		if(newRoleNames.size() > 0){
			for(String newRoleName : newRoleNames){
				userAccount.addRole(newRoleName);
			}
		}
		return removeRoles;
	}
	
}
